/*
 * This file is part of din.
 *
 * din is copyright (c) 2006 - 2012 S Jagannathan <jag@dinisnoise.org>
 * For more information, please visit http://dinisnoise.org
 *
 * din is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * din is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with din.  If not, see <http://www.gnu.org/licenses/>.
 *
*/
#include "multi_curve.h"
#include "vector2d.h"
#include "container.h"

#include "random.h"
using namespace std;

static char eol = '\n';

multi_curve::multi_curve (const string& filename) {
  load (filename);
}

multi_curve::multi_curve () {
  clear ();
}

void multi_curve::copy (const multi_curve& src) {

  if (this != &src) {

    name = src.name;

    r = src.r;
    g = src.g;
    b = src.b;

    vertices.clear (); left_tangents.clear (); right_tangents.clear ();
    for (int i = 0, j = src.vertices.size (); i < j; ++i) {
      vertices.push_back (src.vertices[i]);
      left_tangents.push_back (src.left_tangents[i]);
      right_tangents.push_back (src.right_tangents[i]);
    }

    curv.clear ();
    eval.clear ();
    
    for (int i = 0, j = src.curv.size (); i < j; ++i) {
      curv.push_back (src.curv[i]);
      eval.push_back (src.eval[i]);
    }

    resolution = src.resolution;

  }
}

multi_curve::multi_curve (const multi_curve& src) {
  copy (src);
}

multi_curve& multi_curve::operator= (const multi_curve& src) {
  copy (src);
  return *this;
}

int multi_curve::num_vertices () {
  return vertices.size ();
}

void multi_curve::add_vertex (double x, double y) {
  vertices.push_back (point<double> (x, y));
  if (num_vertices() > 1) {
    curv.push_back (curve());
    eval.push_back (1);
  }
}

void multi_curve::add_left_tangent (double x, double y) {
  left_tangents.push_back (point<double> (x, y));
}

void multi_curve::add_right_tangent (double x, double y) {
  right_tangents.push_back (point<double> (x, y));
}

void multi_curve::get_vertex (int i, double& x, double& y) {
  point<double>& v = vertices[i];
  x = v.x;
  y = v.y;
}

bool multi_curve::set_vertex (int i, double x, double y, int carry_tangents) {
  point<double>& v = vertices[i];
  if ((v.x != x) || (v.y != y)) {
    double dlx, dly, drx, dry;
    if (carry_tangents) {
      point<double>& lt = left_tangents[i];
      point<double>& rt = right_tangents[i];
      dlx = lt.x - v.x; dly = lt.y - v.y;
      drx = rt.x - v.x; dry = rt.y - v.y;
      lt.x = x + dlx; lt.y = y + dly;
      rt.x = x + drx; rt.y = y + dry;
    }
    v.x = x;
    v.y = y;
    eval[i] = 1;
    eval[max (0, i - 1)] = 1;
    return true;
  } else return false;
}

bool multi_curve::set_left_tangent (int i, double x, double y) {
  point<double>& lt = left_tangents [i];
  if ((lt.x != x) || (lt.y != y)) {
    lt.x = x;
    lt.y = y;
    int j = i - 1;
    if (j > -1) eval[j] = 1;
    return true;
  } else return false;
}

bool multi_curve::set_right_tangent (int i, double x, double y) {
  point<double>& rt = right_tangents[i];
  if ((rt.x != x) || (rt.y != y)) {
    rt.x = x;
    rt.y = y;
    if (i < (int)eval.size()) eval[i] = 1;
    return true;
  } else return false;
}

void multi_curve::get_left_tangent (int i, double& x, double& y) {
  point<double>& lt = left_tangents [i];
  x = lt.x;
  y = lt.y;
}

void multi_curve::get_right_tangent (int i, double& x, double& y) {
  point<double>& rt = right_tangents[i];
  x = rt.x;
  y = rt.y;
}

bool multi_curve::insert (double x, double y, double tx, double ty) {
  points_array::iterator vter = ++vertices.begin (), lter = ++left_tangents.begin (), rter = ++right_tangents.begin ();
  vector<int>::iterator eter = eval.begin ();
  vector<curve>::iterator cter = curv.begin ();
  for (int i = 0, j = vertices.size() - 1; i < j; ++i, ++vter, ++lter, ++rter, ++eter, ++cter) {
    point<double>& lt = vertices[i];
    point<double>& rt = vertices[i + 1];
    if ((x >= lt.x) && (x <= rt.x)) {
      double ltx, lty; unit_vector (ltx, lty, x, y, lt.x, lt.y);
      double rtx, rty; unit_vector (rtx, rty, x, y, rt.x, rt.y);
      vertices.insert (vter, point<double>(x, y));
      left_tangents.insert (lter, point<double> (x + tx * ltx, y + ty * lty));
      right_tangents.insert (rter, point<double> (x + tx * rtx, y + ty * rty));
      curv.insert (cter, curve());
      eval[i] = 1;
      eval.insert (eter, 1);
      return true;
    }
  }
  return false;
}

bool multi_curve::remove (int i) {
  // remove ith vertex and its tangents
  if (vertices.size () < 3) return false;
  erase (vertices, i);
  erase (left_tangents, i);
  erase (right_tangents, i);
  if (i == (int) eval.size()) {
    int j = i - 1;
    erase (eval, j);
    erase (curv, j);
  } else {
    erase (eval, i);
    erase (curv, i);
    if (--i > -1) eval[i] = 1;
  }
  return true;
}

void multi_curve::set_resolution (double d) {
  resolution = d;
  force_eval ();
}

void multi_curve::force_eval () {
  for (int i = 0, j = eval.size(); i < j; ++i) eval[i] = 1;
}

double multi_curve::get_resolution () {
  return resolution;
}

void multi_curve::set_color () {
  static const double base = 0.1;
  static rnd<double> rd (base, 1. - base);
  r = rd (); g = rd (); b = rd ();
}

void multi_curve::set_color (double rr, double gg, double bb) {
  r = rr;
  g = gg;
  b = bb;
}

void multi_curve::evaluate () {

  for (int i = 0, last = eval.size(); i < last; ++i) {
    curve& crv = curv[i];
    if (eval[i]) {
      int j = i + 1;
      point<double>& v0 = vertices[i];
      point<double>& v1 = vertices[j];
      point<double>& rt0 = right_tangents[i];
      point<double>& lt1 = left_tangents[j];
      crv.vertex (0, v0.x, v0.y);
      crv.vertex (1, v1.x, v1.y);
      crv.tangent (0, rt0.x, rt0.y);
      crv.tangent (1, lt1.x, lt1.y);
      crv.set_limit (resolution);
      crv.eval ();
      eval[i] = 0;
    }
  }

}

void multi_curve::clear (int all) {

  if (all) {
    name = "noname";
    r = g = b = 1.0;
  }

  vertices.clear ();
  left_tangents.clear ();
  right_tangents.clear ();

  eval.clear ();
  curv.clear ();

  resolution = 0.0005;

}

void multi_curve::load (const string& filename) {
  ifstream file (filename.c_str (), ios::in);
  if (!file) return;
  load (file);

}

void multi_curve::load (ifstream& file) {

  clear ();

  string ignore;

  file >> ignore >> name;

  int nvertices;
  file >> ignore >> nvertices;

  for (int i = 0; i < nvertices; ++i) {

    double x, y;
    file >> ignore >> x >> y;
    add_vertex (x, y);
    file >> ignore >> x >> y;
    add_left_tangent (x, y);
    file >> ignore >> x >> y;
    add_right_tangent (x, y);

  }

  file >> ignore >> resolution;

  file >> ignore >> r >> g >> b;

  evaluate ();

}

void multi_curve::save (const string& filename) {
  ofstream file (filename.c_str (), ios::out);
  if (!file) return;
  save (file);
}

void multi_curve::save (ofstream& file) {

  string ignore;
  file << "name " << name << eol;
  int nvertices = num_vertices ();
  file << "num_vertices " << nvertices << eol;
  for (int i = 0; i < nvertices; ++i) {
    point<double>& v = vertices[i];
    point<double>& lt = left_tangents[i];
    point<double>& rt = right_tangents[i];
    file << "vertex " << v.x << ' ' << v.y << eol;
    file << "left_tangent " << lt.x << ' ' << lt.y << eol;
    file << "right_tangent " << rt.x << ' ' << rt.y << eol;
  }
  file << "curve_resolution " << resolution << eol;
  file << "color " << r << ' ' << g << ' ' << b << eol;

}

void create_polyline (multi_curve& crv, const points_array& pts) {

  int npts = pts.size ();

  if (npts < 2) return;

  crv.clear (0);

  for (int i = 0; i < npts; ++i) {

    double xi = pts[i].x, yi = pts[i].y;
    crv.add_vertex (xi, yi);
    crv.add_left_tangent (xi, yi);
    crv.add_right_tangent (xi, yi);

  }

  crv.evaluate ();

}

void convert2_polyline (multi_curve& crv) {

  for (int i = 0, j = crv.vertices.size(); i < j; ++i) {
    point<double>& v = crv.vertices[i];
    crv.set_left_tangent (i, v.x, v.y);
    crv.set_right_tangent (i, v.x, v.y);
  }

  crv.evaluate ();

}

void convert2_catmull_rom (multi_curve& crv, double tangent_size) {

  int npts = crv.num_vertices ();

  if (npts < 2) return;

  int last = npts - 1;
  point<double>& p0 = crv.vertices[0];
  point<double>& p1 = crv.vertices[1];
  point<double>& pl = crv.vertices[last];
  point<double>& pl1 = crv.vertices[last-1];

  // set tangents for 1st vertex
  double dx, dy; direction (dx, dy, p0.x, p0.y, p1.x, p1.y);
  double tx = tangent_size * dx, ty = tangent_size * dy;
  crv.set_left_tangent (0, p0.x - tx, p0.y - ty);
  crv.set_right_tangent (0, p0.x + tx, p0.y + ty);

  // set tangents for last vertex
  direction (dx, dy, pl.x, pl.y, pl1.x, pl1.y);
  tx = tangent_size * dx; ty = tangent_size * dy;
  crv.set_left_tangent (last, pl.x + tx, pl.y + ty);
  crv.set_right_tangent (last, pl.x - tx, pl.y - ty);

  // set left, right tangent for inbetween vertices
  for (int i = 1; i < last; ++i) {
    int l = i - 1, r = i + 1;
    point<double>& pi = crv.vertices[i];
    point<double>& pl = crv.vertices[l];
    point<double>& pr = crv.vertices[r];
    direction (dx, dy, pl.x, pl.y, pr.x, pr.y);
    crv.set_left_tangent (i, pi.x - tangent_size * dx, pi.y - tangent_size * dy);
    crv.set_right_tangent (i, pi.x + tangent_size * dx, pi.y + tangent_size * dy);
  }

  crv.evaluate ();

}

multi_curve* check_list (multi_curve** lst, int n, const string& name) {
  for (int m = 0; m < n; ++m) if (lst[m]->name == name) return lst[m];
  return 0;
}
